package ;

import hant.PathTools;
import stdlib.FileSystem;
import orm.Db;
import hant.FlashDevelopProject;
import hant.Log;
import haxe.io.Path;
import sys.io.File;
using stdlib.StringTools;

class OrmManagerGenerator 
{
	var log : Log;
	var project : FlashDevelopProject;
	
	public function new(log:Log, project:FlashDevelopProject)
    {
		this.log = log;
		this.project = project;
	}
	
	public function make(db:Db, table:OrmTable, customOrmClassName:String, srcPath:String) : Void
	{
		log.start(table.tableName + " => " + table.customManagerClassName);
		
		var vars = OrmTools.fields2vars(db.connection.getFields(table.tableName));
		
		var autoGeneratedManager = getAutogenManager(db, table.tableName, vars, table.customModelClassName, table.autogenManagerClassName, customOrmClassName);
		var destFileName = srcPath + table.autogenManagerClassName.replace('.', '/') + '.hx';
		FileSystem.createDirectory(Path.directory(destFileName));
		File.saveContent(
			 destFileName
			,"// This is autogenerated file. Do not edit!\n\n" + autoGeneratedManager.toString()
		);
		
		if (project.findFile(table.customManagerClassName.replace('.', '/') + '.hx') == null)
		{
			var customManager = getCustomManager(table.tableName, vars, table.customModelClassName, table.customManagerClassName, table.autogenManagerClassName);
			var destFileName = srcPath + table.customManagerClassName.replace('.', '/') + '.hx';
			FileSystem.createDirectory(Path.directory(destFileName));
			File.saveContent(destFileName, customManager.toString());
		}
		
		log.finishOk();
	}
	
	function getAutogenManager(db:Db, table:String, vars:List<OrmHaxeVar>, modelClassName:String, autogenManagerClassName:String, customOrmClassName:String) : HaxeClass
	{
		var model:HaxeClass = new HaxeClass(autogenManagerClassName);
		
		model.addVar({ haxeName:"db", haxeType:"orm.Db", haxeDefVal:null }, true);
		model.addVar({ haxeName:"orm", haxeType:customOrmClassName, haxeDefVal:null }, true);
		
		model.addMethod(
			  "new"
			, [ 
				  { haxeName:"db", haxeType:"orm.Db", haxeDefVal:null } 
				, { haxeName:"orm", haxeType:customOrmClassName, haxeDefVal:null } 
			  ]
			, "Void"
			, "this.db = db;\nthis.orm = orm;"
		);
        
        model.addMethod('newModelFromParams', vars, modelClassName,
			  "var _obj = new " + modelClassName + "(db, orm);\n"
			+ Lambda.map(vars, function(v:OrmHaxeVar) return '_obj.' + v.haxeName + ' = ' + v.haxeName + ';').join('\n') + "\n"
			+ "return _obj;",
			true
		);
		
		model.addMethod('newModelFromRow', [ OrmTools.createVar('d', 'Dynamic') ], modelClassName,
			    "var _obj = new " + modelClassName + "(db, orm);\n"
			  + Lambda.map(vars, function(v:OrmHaxeVar) return '_obj.' + v.haxeName + " = Reflect.field(d, '" + v.haxeName + "');").join('\n') + "\n"
			  + "return _obj;"
			, true
		);
		
		var getVars = Lambda.filter(vars, function(v:OrmHaxeVar) return v.isKey);
		if (getVars.length > 0)
		{
			model.addMethod('get', getVars, modelClassName,
				"return getBySqlOne('SELECT * FROM `" + table + "`" + getWhereSql(getVars) + ");"
			);
		}
		
		var createVars = Lambda.filter(vars, function(v:OrmHaxeVar) return !v.isAutoInc);
        var foreignKeys = db.connection.getForeignKeys(table);
        var foreignKeyVars = Lambda.filter(vars, function(v:OrmHaxeVar) return !v.isAutoInc);
		model.addMethod('create', createVars, modelClassName,
            (
                Lambda.exists(createVars, function(v:OrmHaxeVar) return v.name == 'position')
                ? "if (position == null)\n"
                 +"{\n"
                 +"\tposition = db.query('SELECT MAX(`position`) FROM `" + table + "`" 
                    +getWhereSql(getForeignKeyVars(db, table, vars))
                    +").getIntResult(0) + 1;\n"
                 +"}\n\n"
                : ""
            )
			+"db.query('INSERT INTO `" + table + "`("
				+ Lambda.map(createVars, function(v) return "`" + v.name + "`").join(", ")
			+") VALUES (' + "
				+ Lambda.map(createVars, function(v) return "db.quote(" + v.haxeName + ")").join(" + ', ' + ")
			+" + ')');\n"
			+"return newModelFromParams(" + Lambda.map(vars, function(v) return v.isAutoInc ? 'db.lastInsertId()' : v.haxeName).join(", ") + ");"
		);
		
		var deleteVars = Lambda.filter(vars, function(v:OrmHaxeVar) return v.isKey);
		if (deleteVars.length == 0) deleteVars = vars;
		model.addMethod('delete', deleteVars, 'Void',
			"db.query('DELETE FROM `" + table + "`" + getWhereSql(deleteVars) + " + ' LIMIT 1');"
		);
		
		model.addMethod('getAll', [ OrmTools.createVar('_order', 'String', getOrderDefVal(vars)) ], 'Array<' + modelClassName + '>',
			 "return getBySqlMany('SELECT * FROM `" + table + "`' + (_order != null ? ' ORDER BY ' + _order : ''));"
		);
		
		model.addMethod('getBySqlOne', [ OrmTools.createVar('sql', 'String') ], modelClassName,
			 "var rows = db.query(sql + ' LIMIT 1');\n"
			+"if (rows.length == 0) return null;\n"
			+"return newModelFromRow(rows.next());"
		);
		
		model.addMethod('getBySqlMany', [ OrmTools.createVar('sql', 'String') ], 'Array<' + modelClassName + '>',
			 "var rows = db.query(sql);\n"
			+"var list : Array<" + modelClassName + "> = [];\n"
			+"for (row in rows)\n"
			+"{\n"
			+"	list.push(newModelFromRow(row));\n"
			+"}\n"
			+"return list;"
		);
		
        for (fields in db.connection.getUniques(table))
		{
            var vs = Lambda.filter(vars, function(v) return Lambda.has(fields, v.name));
			createGetByMethodOne(table, vars, modelClassName, vs, model);
		}
		
        for (v in getForeignKeyVars(db, table, vars))
        {
            createGetByMethodMany(table, vars, modelClassName, [v], model);
        }
		
		return model;
	}
	
	function getCustomManager(table:String, vars:List<OrmHaxeVar>, modelClassName:String, fullClassName:String, baseClassName:String=null) : HaxeClass
	{
		var model = new HaxeClass(fullClassName, baseClassName);
		
		model.addImport(modelClassName);
		
		return model;
	}
	
	function createGetByMethodOne(table:String, vars:List<OrmHaxeVar>, modelClassName:String, whereVars:List<OrmHaxeVar>, model:HaxeClass) : Void
	{
		if (whereVars == null || whereVars.length == 0) return;
        
        model.addMethod(
			'getBy' + Lambda.map(whereVars, function(v) return OrmTools.capitalize(v.haxeName)).join('And'),
			whereVars, 
			modelClassName,
			
			"return getBySqlOne('SELECT * FROM `" + table + "`" + getWhereSql(whereVars) + ");"
		);
	}
	
	function createGetByMethodMany(table:String, vars:List<OrmHaxeVar>, modelClassName:String, whereVars:Iterable<OrmHaxeVar>, model:HaxeClass) : Void
	{
		if (whereVars == null || !whereVars.iterator().hasNext()) return;

		model.addMethod(
			'getBy' + Lambda.map(whereVars, function(v) return OrmTools.capitalize(v.haxeName)).join('And'),
			Lambda.concat(whereVars, [ OrmTools.createVar('_order', 'String', getOrderDefVal(vars)) ]), 
			'Array<' + modelClassName + '>',
			
			"return getBySqlMany('SELECT * FROM `" + table + "`" + getWhereSql(whereVars) + " + (_order != null ? ' ORDER BY ' + _order : ''));"
		);
	}
	
	function getOrderDefVal(vars:List<OrmHaxeVar>) : String
	{
		var positionVar = Lambda.filter(vars, function(v) return v.name == 'position');
		return positionVar.isEmpty() ? "null" : "'" + positionVar.first().haxeName + "'";
	}
    
    function getWhereSql(vars:Iterable<OrmHaxeVar>) : String
    {
        return vars.iterator().hasNext()
            ? " WHERE " + Lambda.map(vars, function(v) return "`" + v.name + "` = ' + db.quote(" + v.haxeName + ")").join("+ ' AND ")
            : "'";
    }
    
    function getForeignKeyVars(db:Db, table:String, vars:List<OrmHaxeVar>) : List<OrmHaxeVar>
    {
        var foreignKeys = db.connection.getForeignKeys(table);
        var foreignKeyVars = Lambda.filter(vars, function(v)
		{
            return Lambda.exists(foreignKeys, function(fk) return fk.key == v.name);
        });
        return foreignKeyVars;
    }

}